package ad_exercise_4.controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import ad_exercise_4.model.LZWDecompressor;
import ad_exercise_4.model.LZWTree;

/**
 * This class contains methods for compressing and decompressing a file using the LZW algorithm
 * 
 * @author Karen Berntsen (s0815799)
 * @author Erik Stens (s0615374)
 */
public class LZWUtil {
	// set of global variables used for buffering data
	private static int	tempOutput		= -1;
	private static int	tempInput		= -1;
	private static int	tempInputState	= 0;
	
	/**
	 * Used for debugging, shows the bytes of a file as a series of int values
	 * 
	 * @param input
	 * @throws IOException
	 */
	public static void printBytes(InputStream input) throws IOException {
		int next;
		do {
			next = input.read();
			if (next != -1) {
				System.out.println(next);
			}
		} while (next != -1);
		input.close();
	}
	
	public static void compress(InputStream input, OutputStream output) throws IOException {
		tempOutput = -1;
		
		LZWTree lzwTree = new LZWTree();
		int next;
		do {
			next = input.read();
			if (next != -1) {
				int result = lzwTree.findNextOrAdd(next);
				if (result != -1) {
					// System.out.println(result);
					outputResult(output, result, false);
				}
			}
		} while (next != -1);
		// System.out.println(lzwTree.getCurrentCode());
		outputResult(output, lzwTree.getCurrentCode(), false);
		
		if (tempOutput != -1) {
			outputResult(output, 0, true);
		}
		
		input.close();
		output.close();
	}
	
	/**
	 * Used for buffering results before writing them. This is needed since 2 12-bit results are saved as 3 bytes
	 * 
	 * @param output
	 *            OutputStream of the output file
	 * @param result
	 *            last result from the compression algorithm
	 * @param isLast
	 *            boolean for checking if the result is the last result to be written
	 */
	private static void outputResult(OutputStream output, int result, boolean isLast) {
		try {
			if (tempOutput != -1) {
				output.write(tempOutput & 0xF | result << 4);
				if (!isLast) {
					output.write(result >> 4);
				}
				tempOutput = -1;
			} else {
				output.write(result);
				tempOutput = result >> 8;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void decompress(InputStream input, OutputStream output) throws IOException {
		tempInput = -1;
		tempInputState = 0;
		
		int next = input.read();
		
		if (next != -1) {
			LZWDecompressor lzwDecompressor = new LZWDecompressor(next);
			writeNextCodes(lzwDecompressor, output, next);
			
			next = input.read();
			while (next != -1) {
				writeNextCodes(lzwDecompressor, output, next);
				next = input.read();
			}
			
			// System.out.println(lzwTree.getCurrentCode());
		}
		input.close();
		output.close();
	}
	
	/**
	 * Used for buffering bytes of data for reconstructing codes of compressed data to be inserted into the decompressor
	 * 
	 * @param lzwDecompressor
	 *            the decompressor
	 * @param output
	 *            OutputStream of the output file
	 * @param next
	 *            the next byte (represented as an int)
	 */
	private static void writeNextCodes(LZWDecompressor lzwDecompressor, OutputStream output, int next) {
		ArrayList<Integer> data = null;
		switch (tempInputState) {
			case 0:
				tempInput = next;
				tempInputState = 1;
				break;
			case 1:
				data = lzwDecompressor.nextData(tempInput | (next & 15) << 8);
				tempInput = next >> 4 & 15;
				tempInputState = 2;
				break;
			case 2:
				data = lzwDecompressor.nextData(tempInput | next << 4);
				tempInput = -1;
				tempInputState = 0;
		}
		
		if (data != null) {
			for (int nextChar : data) {
				try {
					output.write(nextChar);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
